#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define  POINT_NUM      10
#define  INCH_LOOP_NUM  3   
#define  OUTCH_NUM      8
#define  INCH_NUM       8
#define  RANGE          1 
#define  CFG_CNN_SIZE_X 3
#define  CFG_CNN_SIZE_Y 3
#define  CFG_CNN_SIZE_Z 3
#define  WT_DATA_SIZE   32*4
#define  PE_SHIFT_NUM  10

typedef unsigned long long int uint64;
void pt_rand_generator(int point_num, unsigned long long int *pt_data, unsigned int *pt_x, 
                        unsigned int *pt_y, unsigned int *pt_z){
    int data_num = 0;
    while(data_num < point_num) {
        unsigned int x, y, z;
        unsigned long long int pt_temp;
        srand((unsigned int)time(NULL)*100000000000);
        x = rand() % 20 + 1;
        y = rand() % 20 + 1;
        z = rand() % 20 + 1;
        pt_temp = ((z & 0xfff) << 24) + ((y & 0xfff) << 12) + (x & 0xfff);
        // printf(" x= %d, y = %d, z = %d\n", x, y, z);
        if(data_num == 0) {
            pt_data[0] = pt_temp;
            pt_x[0] = x;
            pt_y[0] = y;
            pt_z[0] = z;
            data_num++;
        } else {
            int is_true = 0;
            for(int i = 0; i < data_num; i++){
                if(pt_temp == pt_data[i]) {
                    is_true = 1;
                    break;
                }
            }
            if(is_true == 0)  {
                pt_data[data_num] = pt_temp;
                pt_x[data_num] = x;
                pt_y[data_num] = y;
                pt_z[data_num] = z;
                data_num++;
                printf("@@@ add pt_data, data_num = %d\n", data_num);
            }
        }
    }
    // print pt_data
    for (int i = 0; i < point_num; i++){
        printf("=== i = %d, pt_data = 0x%llx, pt_z = %d, pt_y = %d, pt_x = %d \n", i, pt_data[i], pt_z[i], pt_y[i], pt_x[i]);
    }
}


void in_rand_generator(int point_num, int inch_loop_num, unsigned long long int *in_data){
    for(int i = 0; i < point_num * (inch_loop_num + 1); i++){
        unsigned long long int data_h = rand(); //0x01010101;
        unsigned long long int data_l = rand(); //0x01010101;

        in_data[i] = (data_h<<32) + data_l;
    }
    for (int i = 0; i < point_num * (inch_loop_num + 1); i++){
        printf("=== i = %d, in_data = 0x%llx \n", i, in_data[i]);
    }
}

void wt_rand_generator(unsigned long long int *wt_data){
    for(int i = 0; i < WT_DATA_SIZE*OUTCH_NUM; i++){
        unsigned long long int data_h = rand(); //0x01010101;//
        unsigned long long int data_l = rand(); //0x01010101;//
        wt_data[i] = (data_h<<32) + data_l;
        // printf("=== i = %d, wt_data = 0x%llx \n", i, wt_data[i]);
    }
}

union Data64to8
{
	unsigned long long int in64;
	signed char out8[8];
}wt64to8,in64to8,out64to8;

void SCONV( int point_num, 
            int inch_loop_num,
            unsigned int *pt_x,
            unsigned int *pt_y,
            unsigned int *pt_z,       
            unsigned long long int *in_data,
            unsigned long long int *wt_data,
            unsigned long long int *out_data
){
    int i, j, och, ich, ich_loop;
    int out_data_golden_32[POINT_NUM][OUTCH_NUM];
    signed char out_data_golden_8[POINT_NUM][OUTCH_NUM];
    for (i = 0; i < POINT_NUM; i++) {
        for(och = 0; och < OUTCH_NUM; och++)
            out_data_golden_32[i][och] = 0;
        for (j = 0; j < POINT_NUM; j++){
            unsigned int kx = pt_x[j]- pt_x[i] + RANGE ;
            unsigned int ky = pt_y[j]- pt_y[i] + RANGE ;
            unsigned int kz = pt_z[j]- pt_z[i] + RANGE ;
            if( kx >= 0 && kx <= 2*RANGE && 
                ky >= 0 && ky <= 2*RANGE && 
                kz >= 0 && kz <= 2*RANGE ) {
                // printf("INTO Compute, i = %d, j  = %d, kx=%d, ky=%d, kz=%d\n", i ,j, kx,ky,kz);
                for (och = 0; och < OUTCH_NUM; och++){
                    for(ich_loop = 0; ich_loop < INCH_LOOP_NUM+1; ich_loop++){
                        // printf("ich_loop = %d \n", ich_loop);
                        int wt_index =  (kx + ky * CFG_CNN_SIZE_X + kz * CFG_CNN_SIZE_Y * CFG_CNN_SIZE_Z) + ich_loop * CFG_CNN_SIZE_Z * CFG_CNN_SIZE_Y * CFG_CNN_SIZE_X;
                        wt64to8.in64 = wt_data[och*WT_DATA_SIZE+wt_index];
                        in64to8.in64 = in_data[(j + ich_loop * POINT_NUM)];
                        for (ich = 0; ich < INCH_NUM; ich++){
                            out_data_golden_32[i][och] += wt64to8.out8[ich] * in64to8.out8[ich];
                        }
                    }
                }
            }
        }
    }    
    for (i = 0; i < POINT_NUM; i++) {
        for(och = 0; och < OUTCH_NUM; och++){
            int temp = out_data_golden_32[i][och] >> PE_SHIFT_NUM;
            if(temp > 127)
                out_data_golden_8[i][och] = 127;
            else if(temp < -128) 
                out_data_golden_8[i][och] = -128;
            else 
                out_data_golden_8[i][och] = temp;
            printf("out_data_golden_32[%d][%d]= %d\n", i, och, temp);
            printf("out_data_golden_8[%d][%d]= 0x%x\n", i, och, out_data_golden_8[i][och]);
        }   
    } 

    for (i = 0; i < POINT_NUM; i++) {
        for(och = 0; och < OUTCH_NUM; och++){
            out64to8.out8[och] = out_data_golden_8[i][och];
        }
        out_data[i] = out64to8.in64;
        printf("out_data[%d]= %llx\n", i, out_data[i]);
    }  


}


int judge_points(const int mapping_golden_x[POINT_NUM*27],
                 const int mapping_golden_y[POINT_NUM*27],
                 const int mapping_golden_z[POINT_NUM*27],
                 const int c_mapping_num,
                 const int mapping_x,
                 const int mapping_y,
                 const int mapping_z){
    int i;
    for (i = 0; i < c_mapping_num; i++){
        if(   mapping_golden_x[i]==mapping_x 
           && mapping_golden_y[i]==mapping_y
           && mapping_golden_z[i]==mapping_z)
            return 0;
    }
    return 1;
}


void Pointmapping(
            const unsigned int pt_in_x[POINT_NUM],
            const unsigned int pt_in_y[POINT_NUM],
            const unsigned int pt_in_z[POINT_NUM],
            unsigned int pt_out_x[POINT_NUM*27],
            unsigned int pt_out_y[POINT_NUM*27],
            unsigned int pt_out_z[POINT_NUM*27]
){
    int i, j, c_mapping_num;
    int mapping_x, mapping_y, mapping_z;
    c_mapping_num = 0;
    for (i = 0; i < POINT_NUM; i++) {
        int kx, ky, kz;
        for(kz = 0; kz < 2*RANGE+1; kz++){
            for(ky = 0; ky < 2*RANGE+1; ky++){
                for(kx = 0; kx < 2*RANGE+1; kx++){
                    mapping_x = pt_in_x[i] + kx -  RANGE;
                    mapping_y = pt_in_y[i] + ky -  RANGE;
                    mapping_z = pt_in_z[i] + kz -  RANGE;
                    int judge_result = judge_points(pt_out_x, 
                                                    pt_out_y,
                                                    pt_out_z,
                                                    c_mapping_num,
                                                    mapping_x,
                                                    mapping_y,
                                                    mapping_z);
                    if(judge_result){
                         pt_out_x[c_mapping_num] = mapping_x;
                         pt_out_y[c_mapping_num] = mapping_y;
                         pt_out_z[c_mapping_num] = mapping_z;
                        c_mapping_num++;
                    }
                    // printf("Test, z = %d, y=%d, x = %d, judge_result=%d\n", mapping_z,mapping_y,mapping_x,judge_result);
                }
            }
        }
    }

    //Print result
    printf("@@@@@ Point Mapping Finish! @@@@\n");
    for (i = 0; i < POINT_NUM; i++){
        printf(" ===  pt_in[%6d] = (%6d, %6d, %6d) ===\n", i, pt_in_z[i], pt_in_y[i], pt_in_x[i]);
    }    
    printf("Total mapped point num: %d\n", c_mapping_num);
    for (i = 0; i < c_mapping_num; i++){
        printf(" === pt_out[%6d] = (%6d, %6d, %6d) ===\n", i, pt_out_z[i], pt_out_y[i], pt_out_x[i]);
    }
    printf("Total mapped point num: %d\n", c_mapping_num);
    for (i = 0; i < c_mapping_num; i++){
        uint64 temp = ((uint64)pt_out_z[i]<<24) | ((uint64)pt_out_y[i])<<12 | (uint64)pt_out_x[i];
        printf(" === pt_out_hex[%6d] = 0x%llx ===\n", i, temp);
    }

}




void write_bin_file(const char *filename, unsigned long long int *buffer, size_t size){
    FILE *file = fopen(filename, "wb"); // 打开文件，以二进制写入方式打开    
    if (file != NULL) {        
        fwrite(buffer, sizeof(unsigned long long int), size, file); // 将数据写入文件        
        fclose(file); // 关闭文件    
    } 
}
void read_bin_file(const char *filename, unsigned long long int *buffer, size_t size){
    FILE *file;
    file = fopen(filename, "rb");
    if (file == NULL) {
        perror("Error opening file");
    } 
    size_t items_read = fread(buffer, sizeof(unsigned long long int), size, file);
 
    if (items_read != size) {
        perror("Error reading file");
    }
 
    fclose(file); // 关闭文件
}

void write_txt_file(const char *filename, unsigned long long int *buffer, size_t size){
    FILE *file;
    file = fopen(filename, "w");

    for(int i = 0; i < size; i++)
        fprintf(file, "0x%llx\n", buffer[i]);

    fclose(file);
}



int main(void)
{
    // generate PT data 
    unsigned long long int pt_data[POINT_NUM];
    unsigned int pt_x[POINT_NUM];
    unsigned int pt_y[POINT_NUM];
    unsigned int pt_z[POINT_NUM];
    unsigned long long int in_data[POINT_NUM*(INCH_LOOP_NUM+1)];
    unsigned long long int wt_data[WT_DATA_SIZE*OUTCH_NUM];
    unsigned long long int out_data[POINT_NUM];
    
    pt_rand_generator(POINT_NUM, pt_data, pt_x, pt_y, pt_z);
    write_bin_file("./files/pt_data_1024.bin", pt_data, sizeof(pt_data)/8);

    in_rand_generator(POINT_NUM, INCH_LOOP_NUM ,in_data);
    write_bin_file("./files/in_data.bin", in_data, sizeof(in_data)/8);

    wt_rand_generator(wt_data);
    write_bin_file("./files/wt_data.bin", wt_data, sizeof(wt_data)/8);

    SCONV(POINT_NUM, INCH_LOOP_NUM, pt_x, pt_y, pt_z, in_data, wt_data, out_data);
    write_txt_file("./files/out_data.txt", out_data, sizeof(out_data)/8);    

    unsigned int pt_out_x[POINT_NUM*27];
    unsigned int pt_out_y[POINT_NUM*27];
    unsigned int pt_out_z[POINT_NUM*27];
    Pointmapping(pt_x, pt_y, pt_z, pt_out_x, pt_out_y, pt_out_z);


    // // Test
    // unsigned int pt_out_x[POINT_NUM*27];
    // unsigned int pt_out_y[POINT_NUM*27];
    // unsigned int pt_out_z[POINT_NUM*27];
    // pt_z[0] = 2; pt_y[0] = 2; pt_x[0] = 3;
    // pt_z[1] = 2; pt_y[1] = 3; pt_x[1] = 3; 
    // Pointmapping(pt_x, pt_y, pt_z, pt_out_x, pt_out_y, pt_out_z);




    return 0;

}